using System;
using System.Collections.Generic;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace Game.Network {
    public class UDPClient: IDisposable{
        private Socket m_socket;
        private IPEndPoint m_endpoint;

        private byte[] m_recvBuffer;
        private int m_recvBufferLength;
        private object m_recvLock;
        private List<byte[]> m_recvQueue;

        private List<byte[]> m_sendBuffer;
        private int m_sendBufferPos;
        private Thread m_sendThread;
        private int m_sendTicksPerSecond;

        public Thread SendThread { get { return m_sendThread; } }
        public int SendTicksPerSecond { get { return m_sendTicksPerSecond; } }


        public UDPClient (string hostname, int port, int sendTicksPerSecond = 4){
            m_endpoint = new IPEndPoint (IPAddress.Parse (hostname), port);
            m_socket = new Socket (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            m_sendTicksPerSecond = sendTicksPerSecond;

            m_recvBufferLength = 65535;
            m_recvBuffer = new byte[m_recvBufferLength];
            m_recvQueue = new List<byte[]> ();
            m_recvLock = new object();

            m_sendBuffer = null;

            IPEndPoint temp = new IPEndPoint(IPAddress.Any, 0);
            EndPoint ep = (EndPoint)temp;
            m_socket.BeginReceiveFrom(m_recvBuffer, 0, m_recvBufferLength, SocketFlags.None, ref ep, new AsyncCallback(OnReceive), this);

            m_sendThread = new Thread (new ThreadStart (SendThreadProc));
            m_sendThread.Start ();
        }

        public void Dispose(){
            m_socket.Disconnect (true);
            m_sendThread.Abort ();
            m_sendThread.Join ();
        }

        private static void OnReceive(IAsyncResult result){
            UDPClient client = (UDPClient)result.AsyncState;

            IPEndPoint sender = new IPEndPoint (IPAddress.Any, 0);
            EndPoint ep = (EndPoint)sender;

            int readed = client.m_socket.EndReceiveFrom (result, ref ep);

            if ((readed > 0) && (sender == client.m_endpoint)) {
                byte[] packet = new byte[readed];
                Buffer.BlockCopy(client.m_recvBuffer, 0, packet, 0, readed);
                lock(client.m_recvLock){
                    client.m_recvQueue.Add(packet);
                }
            }
            client.m_socket.BeginReceiveFrom (client.m_recvBuffer, 0, client.m_recvBufferLength, SocketFlags.None, ref ep, new AsyncCallback (OnReceive), client);
        }

        private void SendThreadProc(){
            while (true) {
                DateTime m_start = DateTime.Now;

                m_sendBuffer = UDPPacket.GetPacketSlice();
                if(m_sendBuffer.Count > 0){
                    foreach(byte[] data in m_sendBuffer){
                        m_socket.SendTo(data, m_endpoint);
                    }
                }

                int to_sleep = 1000 / m_sendTicksPerSecond - ((TimeSpan)(DateTime.Now - m_start)).Milliseconds;
                if(to_sleep > 0)
                    Thread.Sleep(to_sleep);
            }
        }

        public void ProcessQueue(){
            List<byte[]> data = null;
            lock (m_recvLock) {
                if( m_recvQueue.Count > 0){
                    data = m_recvQueue;
                    m_recvQueue = new List<byte[]>();
                }
            }
            
            if (data == null)
                return;
            
            foreach (byte[] packet_data in data) {
                PacketHandler handler = UDPPacketHandlers.GetHandler(packet_data[0]);
                PacketReader pr = new PacketReader(packet_data, handler.Length != 0);
                if(handler != null)
                    handler.OnReceive(pr);
            }
        }
    }
}

